package pt.iscte.osgi.contracts.aspects;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

import org.aspectj.lang.reflect.ConstructorSignature;
import org.aspectj.lang.reflect.MethodSignature;

import pt.iscte.osgi.contracts.annotations.Max;
import pt.iscte.osgi.contracts.annotations.MaxLength;
import pt.iscte.osgi.contracts.annotations.Min;
import pt.iscte.osgi.contracts.annotations.Negative;
import pt.iscte.osgi.contracts.annotations.NotNull;
import pt.iscte.osgi.contracts.annotations.Positive;
import pt.iscte.osgi.contracts.annotations.Range;
import pt.iscte.osgi.contracts.annotations.StringNotEmpty;



public abstract aspect ValuePreConditionsAspect {

	private pointcut exclude() : !within(pt.iscte.osgi.contracts.aspects.*+);

	before(): call(*.new(..)) && exclude() {
		//nivel interface
		Constructor<?> constructor = ((ConstructorSignature) thisJoinPoint.getSignature()).getConstructor();
		validateArgs(thisJoinPoint.getArgs(), constructor.getParameterAnnotations());
	}
	
	before(): call(* *.*(..)) && exclude() {
		Method method = ((MethodSignature) thisJoinPoint.getSignature()).getMethod();
		
		//nivel classe
		validateArgs(thisJoinPoint.getArgs(), method.getParameterAnnotations());
		
		//nivel interface
		Class<?> type = thisJoinPoint.getSignature().getDeclaringType();
		for (Class<?> interf : type.getInterfaces()) {			
			for (Method m : interf.getDeclaredMethods()) {
				if(m.getName().equals(method.getName())){					
					validateArgs(thisJoinPoint.getArgs(), m.getParameterAnnotations());
				}
			}
		}
	}

	private void validateArgs(Object[] args, Annotation[][] annotations) {
		for (int i = 0; i < args.length; i++) {
			Object arg = args[i];
			for (Annotation ann : annotations[i]) {
				if (arg == null && ann instanceof NotNull) {
//					System.out.println("Argument cannot be null (index: " + i + ")");
					throw new NullPointerException("Argument cannot be null (index: " + i + ")");
				} else {
					if (arg != null) {
						validateAnnotation(ann, arg);
					}
				}
			}
		}
	}

	private void validateAnnotation(Annotation annotation, Object arg) {		
		if (String.class.isInstance(arg)) {
			if (annotation instanceof MaxLength) {
				int max = ((MaxLength) annotation).value();
				if (((String) arg).length() > max)
//					System.out.println("Invalid string length (max: " + max + ") - " + arg);
					throw new IllegalArgumentException("Invalid string length (max: " + max + ") - " + arg);
			} else {
				if (annotation instanceof StringNotEmpty) {
					if (((String) arg).length() == 0)
//						System.out.println("String cannot be empty!");
						throw new IllegalArgumentException("String cannot be empty!");
				}
			}
		} else {
			if(Integer.class.isInstance(arg) || Double.class.isInstance(arg) || Float.class.isInstance(arg)){
				if (annotation instanceof Positive) {
					if (getDoubleValue(arg) < 0) {
						throw new IllegalArgumentException("arg must be positive");
					}
				} else {
					if (annotation instanceof Negative) {
						if (getDoubleValue(arg) > 0) {
							throw new IllegalArgumentException(
									"arg must be negative");
						}
					} else {
						if (annotation instanceof Max) {
							double max = ((Max) annotation).value();
							if (getDoubleValue(arg) > max) {
//								System.out.println("arg must be smaller or equal than " + max);
								 throw new IllegalArgumentException("arg must be smaller or equal than " + max);
							}
						} else {
							if (annotation instanceof Min) {
								double min = ((Min) annotation).value();
								if (getDoubleValue(arg) < min) {
//									System.out.println("arg must be larger or equal than " + min);
									 throw new IllegalArgumentException("arg must be larger or equal than " + min);
								}
							} else {
								if (annotation instanceof Range) {
									double min = ((Range) annotation).min();
									double max = ((Range) annotation).max();
									Double value = getDoubleValue(arg);
									if (value < min || value > max) {
//										System.out.println("arg out of range (" + min + " to " + max + ")");
										 throw new IllegalArgumentException("arg out of range (" + min + " to " + max + ")");
									}
								}
							}
						}
					}
				}
			}
		}
	}
	
	private double getDoubleValue(Object arg){
		Double value = null;				
		
		if(Integer.class.isInstance(arg)){
			int v = (Integer) arg;
			value = (double) v;		
		} else {
			if(Double.class.isInstance(arg)){
				value = (Double) arg;		
			} else {
				float v = (Float) arg;
				value = (double) v;	
			}
		} 
		
		return value;
	}
	
	after() returning(Object obj): call(* *.get*(..)) && exclude() {		
		Method method = ((MethodSignature) thisJoinPoint.getSignature()).getMethod();
		
		//nivel classe
		annotationsFor(method.getAnnotations(), obj);
		
		//nivel interface
		Class<?> type = thisJoinPoint.getSignature().getDeclaringType();
		for (Class<?> interf : type.getInterfaces()) {			
			for (Method m : interf.getDeclaredMethods()) {
				if(m.getName().equals(method.getName())){	
					annotationsFor(m.getAnnotations(), obj);
				}
			}
		}
	}
	
	private void annotationsFor(Annotation[] annotations, Object obj){
		for (Annotation ann : annotations){
			if(obj != null){
				validateAnnotation(ann, obj);
			}
		}
	}
}
